﻿using System;
using System.Runtime.InteropServices; // DllImport
using System.Text;

namespace myUtils
{
    /// <summary>
    /// The Local Security Authority (LSA) Utility
    /// </summary>

    public class LSAUtility
    {

        #region Import the LSA functions

        [DllImport("advapi32.dll", PreserveSig = true)]
        private static extern Int32 LsaOpenPolicy(ref LSA_UNICODE_STRING SystemName, ref LSA_OBJECT_ATTRIBUTES ObjectAttributes, Int32 DesiredAccess, out IntPtr PolicyHandle);

        [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true, PreserveSig = true)]
        private static extern bool LookupAccountName(string lpSystemName, string lpAccountName, IntPtr psid, ref int cbsid, StringBuilder domainName, ref int cbdomainLength, ref int use);

        [DllImport("advapi32.dll", SetLastError = true, PreserveSig = true)]
        private static extern Int32 LsaAddAccountRights(IntPtr PolicyHandle, IntPtr AccountSid, LSA_UNICODE_STRING[] UserRights, int CountOfRights);

        [DllImport("advapi32.dll")]
        private static extern bool IsValidSid(IntPtr pSid);

        [DllImport("advapi32")]
        public static extern void FreeSid(IntPtr pSid);

        [DllImport("advapi32.dll")]
        private static extern Int32 LsaClose(IntPtr ObjectHandle);

        [DllImport("advapi32.dll")]
        private static extern Int32 LsaNtStatusToWinError(Int32 status);

        [DllImport("kernel32.dll")]
        private static extern int GetLastError();

        #endregion Import the LSA functions

        #region Structures

        #region LSA_OBJECT_ATTRIBUTES

        [StructLayout(LayoutKind.Sequential)]
        private struct LSA_OBJECT_ATTRIBUTES
        {

            public int Attributes;
            public int Length;

            public IntPtr RootDirectory;
            public IntPtr SecurityDescriptor;
            public IntPtr SecurityQualityOfService;

            public LSA_UNICODE_STRING ObjectName;

        } // LSA_OBJECT_ATTRIBUTES

        #endregion LSA_OBJECT_ATTRIBUTES

        #region LSA_UNICODE_STRING

        [StructLayout(LayoutKind.Sequential)]
        private struct LSA_UNICODE_STRING
        {

            public UInt16 Length;
            public UInt16 MaximumLength;

            // NOTE: Buffer has to be declared after Length and MaximumLength;
            // otherwise, you will get winErrorCode: 1734 (The array bounds are invalid.)
            // and waste lots of time trying to track down what causes the error!
            public IntPtr Buffer;

        } // LSA_UNICODE_STRING

        #endregion LSA_UNICODE_STRING

        #endregion Structures

        #region Enumeration

        #region LSA_AccessPolicy

        private enum LSA_AccessPolicy : long
        {

            POLICY_VIEW_LOCAL_INFORMATION = 0x00000001L,
            POLICY_VIEW_AUDIT_INFORMATION = 0x00000002L,
            POLICY_GET_PRIVATE_INFORMATION = 0x00000004L,
            POLICY_TRUST_ADMIN = 0x00000008L,
            POLICY_CREATE_ACCOUNT = 0x00000010L,
            POLICY_CREATE_SECRET = 0x00000020L,
            POLICY_CREATE_PRIVILEGE = 0x00000040L,
            POLICY_SET_DEFAULT_QUOTA_LIMITS = 0x00000080L,
            POLICY_SET_AUDIT_REQUIREMENTS = 0x00000100L,
            POLICY_AUDIT_LOG_ADMIN = 0x00000200L,
            POLICY_SERVER_ADMIN = 0x00000400L,
            POLICY_LOOKUP_NAMES = 0x00000800L,
            POLICY_NOTIFICATION = 0x00001000L

        } // LSA_AccessPolicy

        #endregion LSA_AccessPolicy

        #endregion Enumeration

        #region Methods

        /// <summary>Adds a privilege to an account</summary>
        /// <param name="accountName">Name of an account - "domain\account" or only "account"</param>
        /// <param name="privilegeName">Name of the privilege</param>
        /// <returns>The windows error code returned by LsaAddAccountRights</returns>
        public static Int32 SetRight(string accountName, string privilegeName, out string errorMessage)
        {

            int accountType = 0; // account-type variable for lookup
            int nameSize = 0; // size of the domain name
            int sidSize = 0; // size of the SID

            IntPtr sid = IntPtr.Zero; // pointer for the SID

            Int32 winErrorCode = 0; // contains the last error

            StringBuilder domainName = null; // StringBuilder for the domain name

            errorMessage = "";

            // get required buffer size
            LookupAccountName(null, accountName, sid, ref sidSize, domainName, ref nameSize, ref accountType);

            // allocate buffers
            domainName = new StringBuilder(nameSize);

            sid = Marshal.AllocHGlobal(sidSize);

            // lookup the SID for the account
            if (!LookupAccountName(null, accountName, sid, ref sidSize, domainName, ref nameSize, ref accountType))
            {
                winErrorCode = GetLastError();

                errorMessage = "An error occurred while Looking up the Account Name, Error Code: " + winErrorCode;
            } // end if

            else
            {
                // combine all policies
                int access = (int)(LSA_AccessPolicy.POLICY_AUDIT_LOG_ADMIN |
                LSA_AccessPolicy.POLICY_CREATE_ACCOUNT |
                LSA_AccessPolicy.POLICY_CREATE_PRIVILEGE |
                LSA_AccessPolicy.POLICY_CREATE_SECRET |
                LSA_AccessPolicy.POLICY_GET_PRIVATE_INFORMATION |
                LSA_AccessPolicy.POLICY_LOOKUP_NAMES |
                LSA_AccessPolicy.POLICY_NOTIFICATION |
                LSA_AccessPolicy.POLICY_SERVER_ADMIN |
                LSA_AccessPolicy.POLICY_SET_AUDIT_REQUIREMENTS |
                LSA_AccessPolicy.POLICY_SET_DEFAULT_QUOTA_LIMITS |
                LSA_AccessPolicy.POLICY_TRUST_ADMIN |
                LSA_AccessPolicy.POLICY_VIEW_AUDIT_INFORMATION |
                LSA_AccessPolicy.POLICY_VIEW_LOCAL_INFORMATION);

                Int32 result; // Operation result

                IntPtr policyHandle = IntPtr.Zero; // initialize a pointer for the policy handle

                LSA_UNICODE_STRING systemName = new LSA_UNICODE_STRING(); // initialize an empty unicode-string

                // this variable and it's attributes are not used, but LsaOpenPolicy wants them to exists
                LSA_OBJECT_ATTRIBUTES ObjectAttributes = new LSA_OBJECT_ATTRIBUTES();

                // get a policy handle
                result = LsaOpenPolicy(ref systemName, ref ObjectAttributes, access, out policyHandle);

                winErrorCode = LsaNtStatusToWinError(result);

                if (winErrorCode != 0)
                {
                    errorMessage = "An error occurred while opening the policy, Error Code: " + winErrorCode;
                } // end if

                else // Now that we have the SID and the policy, we can add the right to the account.
                {
                    // initialize a unicode-string for the privilege name
                    LSA_UNICODE_STRING[] userRights = new LSA_UNICODE_STRING[1];

                    userRights[0] = new LSA_UNICODE_STRING();
                    userRights[0].Buffer = Marshal.StringToHGlobalUni(privilegeName);
                    userRights[0].Length = (UInt16)(privilegeName.Length * UnicodeEncoding.CharSize);
                    userRights[0].MaximumLength = (UInt16)((privilegeName.Length + 1) * UnicodeEncoding.CharSize);

                    // add the right to the account
                    result = LsaAddAccountRights(policyHandle, sid, userRights, 1);

                    winErrorCode = LsaNtStatusToWinError(result);

                    if (winErrorCode != 0)
                    {
                        errorMessage = "An error occurred while adding the account right, Error Code: " + winErrorCode;
                    } // end if

                    Marshal.FreeHGlobal(userRights[0].Buffer);

                    LsaClose(policyHandle);
                } // end else

                FreeSid(sid);
            } // end else

            return winErrorCode;

        } // SetRight()

        #endregion Methods

    } // class LSAUtility
} // namespace myUtils
